/************** O T T R   C O P Y R I G H T   N O T I C E **************
 **                                                                   **
 ** © 2013 OTTR, Inc.  All rights reserved.                           **
 **                                                                   ** 
 *************** O T T R   C O P Y R I G H T   N O T I C E *************/

package com.jonathan.javatar;

import static com.jonathan.javatar.USTarHeader.*;
import static com.jonathan.javatar.V7TarHeader.*;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Pattern;

/**
 *
 * @author Jonathan Paz <jonathan.paz@ottr.com>
 */
class PaxHeader extends TarHeader implements Cloneable, Serializable {
    private USTarHeader extHeader;
    private USTarHeader mainHeader;
    private LinkedHashMap<String,String> properties = new LinkedHashMap<>();
    private transient StringBuilder extContents;

    private static final Pattern PARAM = Pattern.compile("([^=]*)=([^\\n]*)\\n");
    public PaxHeader(byte[] header) {
        super(header);
        extHeader = new USTarHeader(Arrays.copyOf(header, 512));
        int extLen = (int)((extHeader.getSize() + 511) / 512);
        String str = new String(header,513,(int)extHeader.getSize(),StandardCharsets.UTF_8);
        mainHeader = new USTarHeader(Arrays.copyOfRange(header, extLen + 1, extLen + 513));
        Scanner scanner = new Scanner(str);
        while (scanner.hasNextInt()) {
            int len = scanner.nextInt();
            String param = scanner.next(Pattern.compile(".{" + len + "}", Pattern.DOTALL));
        }
    }

    public synchronized String getUname() {
        if (properties.containsKey("uname")) {
            return properties.get("uname");
        }
        return mainHeader.getUname();
    }

    public void setUname(String uname) throws IllegalArgumentException {
        if (uname.length() > UNAME_LENGTH) {
            mainHeader.setUname(null);
            synchronized(this) {
                properties.put("uname", uname);
            }
        } else {
            if (properties.containsKey("uname")) {
                synchronized(this) {
                    properties.remove("uname");
                }
            }
            mainHeader.setUname(uname);
        }
    }

    public synchronized String getGname() {
        if (properties.containsKey("gname")) {
            return properties.get("gname");
        }
        return mainHeader.getGname();
    }

    public synchronized void setGname(String gname) throws IllegalArgumentException {
        if (gname.length() > GNAME_LENGTH) {
            mainHeader.setGname(null);
            properties.put("gname", gname);
        } else {
            if (properties.containsKey("gname")) {
                properties.remove("gname");
            }
            mainHeader.setGname(gname);
        }
    }

    public int getDevmajor() {
        return mainHeader.getDevmajor();
    }

    public void setDevmajor(int devmajor) throws IllegalArgumentException {
        mainHeader.setDevmajor(devmajor);
    }

    public int getDevminor() {
        return mainHeader.getDevminor();
    }

    public void setDevminor(int devminor) throws IllegalArgumentException {
        mainHeader.setDevminor(devminor);
    }

    public synchronized String getName() throws IllegalArgumentException {
        if (properties.containsKey("name")) {
            return properties.get("name");
        }
        return mainHeader.getName();
    }

    public void setName(String name) {
        try {
            mainHeader.setName(name);
        } catch (IllegalArgumentException e) {
            mainHeader.setName(null);
            synchronized(this) {
                properties.put("path",name);
            }
        }
    }

    public int getMode() {
        return mainHeader.getMode();
    }

    public void setMode(int mode) throws IllegalArgumentException {
        mainHeader.setMode(mode);
    }

    public synchronized int getUid() {
        if (properties.containsKey("uid")) {
            return Integer.parseInt(properties.get("uid"));
        }
        return mainHeader.getUid();
    }

    public synchronized void setUid(int uid) throws IllegalArgumentException {
        if (uid > UID_MAXVAL) {
            mainHeader.setUid(0);
            properties.put("uid",Integer.toString(uid));
        } else {
            properties.remove("uid");
            mainHeader.setUid(uid);
        }
    }

    public synchronized int getGid() {
        if (properties.containsKey("gid")) {
            return Integer.parseInt(properties.get("gid"));
        }
        return mainHeader.getGid();
    }

    public synchronized void setGid(int gid) throws IllegalArgumentException {
        if (gid > GID_MAXVAL) {
            mainHeader.setGid(0);
            properties.put("gid",Integer.toString(gid));
        } else {
            properties.remove("gid");
            mainHeader.setGid(gid);
        }
    }

    public synchronized long getSize() {
        if (properties.containsKey("size")) {
            return Long.parseLong(properties.get("size"));
        }
        return mainHeader.getSize();
    }

    public synchronized void setSize(long size) throws IllegalArgumentException {
        if (size > SIZE_MAXVAL) {
            properties.put("size",Long.toString(size));
            mainHeader.setSize(0);
        } else {
            properties.remove("size");
            mainHeader.setSize(size);
        }
    }

    public synchronized long getMtime() {
        if (properties.containsKey("mtime")) {
            return Long.parseLong(properties.get("mtime"));
        }
        return mainHeader.getMtime();
    }

    public synchronized void setMtime(long mtime) throws IllegalArgumentException {
        if (mtime > MTIME_MAXVAL || mtime < MTIME_MINVAL) {
            mainHeader.setMtime(0);
            properties.put("mtime",Long.toString(mtime));
        } else {
            properties.remove("mtime");
            mainHeader.setMtime(mtime);
        }
    }

    public char getTypeflag() {
        return mainHeader.getTypeflag();
    }

    public void setTypeflag(char typeflag) {
        mainHeader.setTypeflag(typeflag);
    }

    public synchronized String getLinkname() {
        if (properties.containsKey("linkpath")) {
            return properties.get("linkpath");
        }
        return mainHeader.getLinkname();
    }

    public synchronized void setLinkname(String linkname) throws IllegalArgumentException {
        if (linkname.length() > LINKNAME_LENGTH) {
            mainHeader.setLinkname(null);
            properties.put("linkpath",linkname);
        } else {
            properties.remove("linkpath");
            mainHeader.setLinkname(linkname);
        }
    }

    public synchronized long getAtime() {
        return Long.parseLong(properties.get("atime"));
    }

    public synchronized void setAtime(long atime) {
        properties.put("atime",Long.toString(atime));
    }

    public synchronized long getCtime() {
        return Long.parseLong(properties.get("ctime"));
    }

    public synchronized void setCtime(long Ctime) {
        properties.put("ctime",Long.toString(Ctime));
    }

    public synchronized String getCharset() {
        return properties.get("charset");
    }

    public synchronized void setCharset(String charset) {
        if (charset == null) {
            properties.remove("charset");
        } else {
            properties.put("charset",charset);
        }
    }

    public synchronized String getComment() {
        return properties.get("comment");
    }

    public synchronized void setComment(String comment) {
        if (comment == null) {
            properties.remove("comment");
        } else {
            properties.put("comment",comment);
        }
    }

    @Override
    public synchronized byte[] getHeaderBlock() {
        prepareExtHeader();
        int extlen = (extContents.length() + 511) / 512;
        byte[] header = new byte[1024 + extlen];
        System.arraycopy(extHeader.getHeaderBlock(), 0, header, 0, 512);
        System.arraycopy(extContents.toString().getBytes(StandardCharsets.UTF_8),0,header,512,extlen);
        System.arraycopy(mainHeader.getHeaderBlock(), 0, header, 512 + extlen, 512);
        extContents = null;
        return header;
    }

    @Override
    void calculateChksum() {
        // no checksum to calculate
    }

    private synchronized void prepareExtHeader() {
        extContents = new StringBuilder();
        for (Map.Entry<String,String> entry : properties.entrySet()) {
            extContents.append(extHdrEntry(entry.getKey(), entry.getValue()));
        }
        extHeader.setSize(extContents.length());
    }

    private String extHdrEntry(String name, String value) {
        String hdrpart = String.format("%s=%s\n",name,value);
        int strlen = hdrpart.length() + 1;
        int lendgts = ((int)Math.log10(strlen)) + 1;
        int len = strlen + lendgts;
        String hdr = String.format("%d %s=%s\n",len,name,value);
        while (hdr.length() > len) {
            ++len;
            hdr = String.format("%d %s=%s\n",len,name,value);
        }
        return hdr;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}
